﻿using Sharp.WeShare.Common;
using Sharp.WeShare.MP.EnumKey;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace Sharp.WeShare.MP.MsgEntity
{
    public class BaseMsg
    {
        /// <summary>
        /// 开发者微信号
        /// </summary>
        public string ToUserName { get; set; }
        /// <summary>
        /// 发送方账号（一个openid）
        /// </summary>
        public string FromUserName { get; set; }
        /// <summary>
        /// 消息创建时间 （整型）
        /// </summary>
        public string CreateTime { get; set; }
        /// <summary>
        /// 消息类型
        /// </summary>
        public string MsgType { get; set; }
        /// <summary>
        /// 普通消息专用。 事件消息中，此字段为空
        /// </summary>
        public string MsgId { get; set; }
        /// <summary>
        /// 多线程锁专用
        /// </summary>
        private static object locker = new object();
        /// <summary>
        /// 历史消息表示，用于消息排重
        /// </summary>
        private static ConcurrentDictionary<string, DateTime> historymsg = new ConcurrentDictionary<string, DateTime>();
        /// <summary>
        /// 用于定期移除过期的消息
        /// </summary>
        private static Thread thread;
        /// <summary>
        /// 根据请求的实例，绑定事件
        /// </summary>
        /// <param name="param">事件接口</param>
        /// <param name="param">接入参数</param>
        /// <param name="xml">测试xml，正式运行的时候此参数为空</param>
        /// <returns></returns>
        private static void BindByHandlerByInStance(EnterParam param, IMsgHandler handler, string xml = "")
        {
            var doc = XElement.Parse(xml);
            var msgtype = (MessageType)Enum.Parse(typeof(MessageType), doc.Element("MsgType").Value.ToUpper());

            var msg = new BaseMsg();
            switch (msgtype)
            {
                case EnumKey.MessageType.TEXT:
                    handler.TextHandler(Utils.XmlToObject<TextMsg>(xml));
                    break;
                case EnumKey.MessageType.IMAGE:
                    handler.ImageHandler(Utils.XmlToObject<ImgMsg>(xml));
                    break;
                case EnumKey.MessageType.VOICE:
                    handler.VoiceHandler(Utils.XmlToObject<VoiceMsg>(xml));
                    break;
                case EnumKey.MessageType.VIDEO:
                    handler.VideoHandler(Utils.XmlToObject<VideoMsg>(xml));
                    break;
                case EnumKey.MessageType.LOCATION:
                    handler.LocationHandler(Utils.XmlToObject<LocationMsg>(xml));
                    break;
                case EnumKey.MessageType.LINK:
                    handler.LinkHandler(Utils.XmlToObject<LinkMsg>(xml));
                    break;
                case MessageType.EVENT:
                    var eventtype = (EventType)Enum.Parse(typeof(EventType), doc.Element("Event").Value.ToUpper());
                    switch (eventtype)
                    {
                        case EventType.SUBSCRIBE:
                            handler.SubscribeHandler(Utils.XmlToObject<SubscribeEventMsg>(xml));
                            break;
                        case EventType.UNSUBSCRIBE:
                            handler.UnSubscribeHandler(Utils.XmlToObject<EventMsg>(xml));
                            break;
                        case EventType.SCAN:
                            handler.ScanHandler(Utils.XmlToObject<ScanQrEventMsg>(xml));
                            break;
                        case EventType.LOCATION:
                            handler.LocationEventHandler(Utils.XmlToObject<LocationEventMsg>(xml));
                            break;
                        case EventType.CLICK:
                            handler.MenuClickHandler(Utils.XmlToObject<BaseMenuEventMsg>(xml));
                            break;
                        case EventType.VIEW:
                            handler.MenuViewHandler(Utils.XmlToObject<ViewEventMsg>(xml));
                            break;
                        case EventType.SCANCODE_PUSH:
                            handler.MenuScanPushHandler(Utils.XmlToObject<ScanMenuEventMsg>(xml));
                            break;
                        case EventType.SCANCODE_WAITMSG:
                            handler.MenuScanWaitHandler(Utils.XmlToObject<ScanMenuEventMsg>(xml));
                            break;
                        case EventType.PIC_SYSPHOTO:
                        case EventType.PIC_PHOTO_OR_ALBUM:
                        case EventType.PIC_WEIXIN:
                            handler.MenuPicHandler(Utils.XmlToObject<PicMenuEventMsg>(xml));
                            break;
                        case EventType.LOCATION_SELECT:
                            handler.MenuLocationHandler(Utils.XmlToObject<LocationMenuEventMsg>(xml));
                            break;
                        case EventType.TEMPLATESENDJOBFINISH:
                            handler.TemplateJobEventHandler(Utils.XmlToObject<TemplateJobEventMsg>(xml));
                            break;
                        case EventType.MASSSENDJOBFINISH:
                            handler.GroupSendJobHandler(Utils.XmlToObject<GroupSendJobEventMsg>(xml));
                            break;
                        case EventType.KF_CREATE_SESSION:
                            break;
                        case EventType.KF_CLOSE_SESSION:
                            break;
                        case EventType.KF_SWITCH_SESSION:
                            break;
                        case EventType.MERCHANT_ORDER:
                            break;
                        case EventType.POI_CHECK_NOTIFY:
                            handler.PoiNotifyHandler(Utils.XmlToObject<PoiNotifyEventMsg>(xml));
                            break;
                        case EventType.CARD_PASS_CHECK:
                            break;
                        case EventType.CARD_NOT_PASS_CHECK:
                            break;
                        case EventType.USER_GET_CARD:
                            handler.UserGetCardHandler(Utils.XmlToObject<UserGetCardEventMsg>(xml));
                            break;
                        case EventType.USER_DEL_CARD:
                            break;
                        case EventType.USER_VIEW_CARD:
                            break;
                        case EventType.USER_CONSUME_CARD:
                            break;

                        case EventType.VERIFY_EXPIRED:
                        case EventType.QUALIFICATION_VERIFY_SUCCESS:
                        case EventType.QUALIFICATION_VERIFY_FAIL:
                        case EventType.NAMING_VERIFY_FAIL:
                        case EventType.NAMING_VERIFY_SUCCESS:
                        case EventType.ANNUAL_RENEW:
                            handler.VerifyHandler(Utils.XmlToObject<VerifyEventMsg>(xml));
                            break;
                        case EventType.USER_PAY_FROM_PAY_CELL:
                            handler.UserPayHandler(Utils.XmlToObject<UserPayEventMsg>(xml));
                            break;
                        case EventType.SUBMIT_MEMBERCARD_USER_INFO:
                            handler.SubmitMemberUserInfoHandler(Utils.XmlToObject<SubmitMemberUserInfoEventMsg>(xml));
                            break;
                        default:
                            break;
                    }
                    break;
                case EnumKey.MessageType.SHORTVIDEO:
                    handler.VideoHandler(Utils.XmlToObject<VideoMsg>(xml));
                    break;

                default:
                    break;
            }
        }

        /// <summary>
        /// 绑定消息与处理事件
        /// </summary>
        /// <param name="mheList"></param>
        /// <param name="param"></param>
        /// <param name="xml"></param>
        public static void BindEvent(IMsgHandler handler, EnterParam param, string xml = "")
        {
            #region 获取消息基本信息
            if (string.IsNullOrEmpty(xml))
            {
                xml = Utils.GetRequestData(param);
                Utils.WriteTxt(xml);
            }
            var doc = XElement.Parse(xml);
            var msgtype = doc.Element("MsgType").Value;
            var FromUserName = doc.Element("FromUserName").Value;
            var CreateTime = doc.Element("CreateTime").Value;
            var ToUserName = doc.Element("ToUserName").Value;
            #endregion

            #region 消息排重
            if (msgtype == "event")
            {
                if (!historymsg.ContainsKey(FromUserName + CreateTime))
                {
                    //添加事件消息
                    historymsg.GetOrAdd(FromUserName + CreateTime, DateTime.Now);
                }
                else
                {
                    return;
                }
            }
            else
            {
                var msgid = doc.Element("MsgId").Value;
                if (!historymsg.ContainsKey(msgid))
                {
                    historymsg.GetOrAdd(msgid, DateTime.Now);
                }
                else
                {
                    return;
                }
            }
            //缓存中的数量大于10000时，创建一个新线程，用于移除过期的消息
            if (historymsg.Count > 10000)
            {
                thread = new Thread(() =>
                {
                    foreach (var history in historymsg)
                    {
                        //移除20s之前的缓存
                        if (history.Value < DateTime.Now.AddSeconds(-20))
                        {
                            DateTime dt;
                            historymsg.TryRemove(history.Key,out dt);
                        }
                    }
                    thread.Abort();
                });
                thread.IsBackground = true;
                thread.Start();
            }
            #endregion
            ///绑定通用回调。
            handler.CommonHandler(
                new BaseMsg
                {
                    CreateTime = CreateTime,
                    FromUserName = FromUserName,
                    MsgType = msgtype,
                    ToUserName = ToUserName
                });
            BaseMsg.BindByHandlerByInStance(param, handler, xml);
        }
        #region 响应请求
        /// <summary>
        /// 回复文本消息
        /// </summary>
        /// <param name="content">消息内容</param>
        /// <param name="param">接入url时的参数</param>
        public void ResponseTxt(string content, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[text]]></MsgType>");
            data.AppendFormat("<Content><![CDATA[{0}]]></Content></xml>", content);
            Response(param, data.ToString());
        }
        /// <summary>
        /// 回复图片消息
        /// </summary>
        /// <param name="mediaId">图片消息的媒体ID</param>
        /// <param name="param"></param>
        public void ResponseImg(string mediaId, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[image]]></MsgType>");
            data.AppendFormat("<Image><MediaId><![CDATA[{0}]]></MediaId></Image></xml>", mediaId);
            Response(param, data.ToString());
        }
        /// <summary>
        /// 回复语音消息
        /// </summary>
        /// <param name="mediaId">语音消息的媒体ID</param>
        /// <param name="param"></param>
        public void ResponseVoice(string mediaId, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[voice]]></MsgType>");
            data.AppendFormat("<Voice><MediaId><![CDATA[{0}]]></MediaId></Voice></xml>", mediaId);
            Response(param, data.ToString());
        }
        /// <summary>
        /// 回复视频
        /// </summary>
        /// <param name="video"></param>
        /// <param name="param"></param>
        public void ResponseVideo(ResponseVideo video, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[video]]></MsgType>");
            data.AppendFormat("<Video><MediaId><![CDATA[{0}]]></MediaId>", video.MediaId);
            data.AppendFormat("<Title><![CDATA[{0}]]></Title>", video.Title);
            data.AppendFormat("<Description><![CDATA[{0}]]></Description></Video></xml>", video.Description);
            Response(param, data.ToString());
        }

        public void ResponseMusic(ResponseMusic music, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[music]]></MsgType>");
            data.AppendFormat("<Title><![CDATA[{0}]]></Title>", music.Title);
            data.AppendFormat("<Description><![CDATA[{0}]]></Description>", music.Description);
            data.AppendFormat("<MusicUrl><![CDATA[{0}]]></MusicUrl>", music.MusicURL);
            data.AppendFormat("<HQMusicUrl><![CDATA[{0}]]></HQMusicUrl>", music.HQMusicUrl);
            data.AppendFormat("<ThumbMediaId><![CDATA[{0}]]></ThumbMediaId></Music></xml>", music.ThumbMediaId);
            Response(param, data.ToString());
        }

        public void ResponseArts(ResponseMusic music, EnterParam param)
        {
            var data = new StringBuilder();
            data.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>", FromUserName);
            data.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>", ToUserName);
            data.AppendFormat("<CreateTime>{0}</CreateTime>", Utils.GetTimeStamp());
            data.AppendFormat("<MsgType><![CDATA[music]]></MsgType>");
            data.AppendFormat("<Title><![CDATA[{0}]]></Title>", music.Title);
            data.AppendFormat("<Description><![CDATA[{0}]]></Description>", music.Description);
            data.AppendFormat("<MusicUrl><![CDATA[{0}]]></MusicUrl>", music.MusicURL);
            data.AppendFormat("<HQMusicUrl><![CDATA[{0}]]></HQMusicUrl>", music.HQMusicUrl);
            data.AppendFormat("<ThumbMediaId><![CDATA[{0}]]></ThumbMediaId></Music></xml>", music.ThumbMediaId);
            Response(param, data.ToString());
        }

        /// <summary>
        /// 转发至多客服。可指定客服人员
        /// </summary>
        /// <param name="param"></param>
        /// <param name="account">客服账号</param>
        public void TransDkf(EnterParam param, string account = "")
        {
            var resxml = new StringBuilder();
            resxml.AppendFormat("<xml><ToUserName><![CDATA[{0}]]></ToUserName>",
            FromUserName);
            resxml.AppendFormat("<FromUserName><![CDATA[{0}]]></FromUserName>",
            ToUserName);
            resxml.AppendFormat("<CreateTime>{0}</CreateTime>",
                Utils.ConvertDateTimeInt(DateTime.Now));
            resxml.Append("<MsgType><![CDATA[transfer_customer_service]]></MsgType>");
            if (account != "")
            {
                resxml.AppendFormat("<TransInfo><KfAccount><![CDATA[{0}]]></KfAccount></TransInfo>", account);
            }
            resxml.AppendFormat("</xml>");
            Response(param, resxml.ToString());
        }

        private void Response(EnterParam param, string data)
        {
            Utils.WriteTxt(data);
            if (param.IsAes)
            {
                var wxcpt = new WXBizMsgCrypt(param.token, param.EncodingAESKey,
                param.appid);
                wxcpt.EncryptMsg(data, Utils.GetTimeStamp().ToString(), Utils.GetTimeStamp().ToString(),
                ref data);
            }
            HttpContext.Current.Response.Write(data);
        }

        #endregion
    }
}
